package service

import (
	"chatplus/core/types"
	"errors"
	"fmt"
	"github.com/goccy/go-json"
	"github.com/imroc/req/v3"
	"github.com/wenlng/go-captcha/captcha"
	"io"
	"io/ioutil"
	"os"
	"strconv"
	"strings"
	"time"
)

type CaptchaService struct {
	config types.ChatPlusApiConfig
	client *req.Client
}

func NewCaptchaService() *CaptchaService {
	return &CaptchaService{}
}

func (s *CaptchaService) Get() (interface{}, error) {
	capt := captcha.GetCaptcha()
	dots, imageBase64, thumbImageBase64, key, err := capt.Generate()
	if err != nil {
		return nil, fmt.Errorf("生成验证码失败：%v", err)
	}
	writeCache(dots, key)
	captchaData := struct {
		Dots        map[int]captcha.CharDot `json:"dots"`
		ImageBase64 string                  `json:"image"`
		ThumbBase64 string                  `json:"thumb"`
		Key         string                  `json:"key"`
	}{
		Dots:        dots,
		ImageBase64: imageBase64,
		ThumbBase64: thumbImageBase64,
		Key:         key,
	}
	res := types.BizVo{
		Code: types.Success,
		Data: captchaData,
	}
	return res.Data, nil
}

func writeCache(v interface{}, file string) {
	bt, _ := json.Marshal(v)
	month := time.Now().Month().String()
	cacheDir := getCacheDir() + month + "/"
	_ = os.MkdirAll(cacheDir, 0660)
	file = cacheDir + file + ".json"
	logFile, _ := os.OpenFile(file, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
	defer logFile.Close()
	// 检查过期文件
	//checkCacheOvertimeFile()
	_, _ = io.WriteString(logFile, string(bt))
}

func (s *CaptchaService) Check(data interface{}) bool {
	if smsData, ok := data.(struct {
		Receiver string `json:"receiver"` // 接收者
		Key      string `json:"key"`
		Dots     string `json:"dots"`
	}); ok {
		key := smsData.Key
		if key == "" {
			return false
		}
		// 通过校验
		return true
	} else {
		if codeData, ok := data.(struct {
			Key  string `json:"key"`
			Dots string `json:"dots"`
		}); ok {
			key := codeData.Key
			dots := codeData.Dots
			if dots == "" || key == "" {
				return false
			}
			cacheData := readCache(key)
			if cacheData == "" {
				return false
			}
			src := strings.Split(dots, ",")
			var dct map[int]captcha.CharDot
			if err := json.Unmarshal([]byte(cacheData), &dct); err != nil {
				return false
			}
			chkRet := false
			if (len(dct) * 2) == len(src) {
				for i, dot := range dct {
					j := i * 2
					k := i*2 + 1
					sx, _ := strconv.ParseFloat(fmt.Sprintf("%v", src[j]), 64)
					sy, _ := strconv.ParseFloat(fmt.Sprintf("%v", src[k]), 64)
					chkRet = captcha.CheckPointDistWithPadding(int64(sx), int64(sy), int64(dot.Dx), int64(dot.Dy), int64(dot.Width), int64(dot.Height), 5)
					if !chkRet {
						break
					}
				}
			}
			if chkRet {
				// 通过校验
				return true
			}

		} else {
			fmt.Println("数据不是预期的类型")
			return false
		}
	}
	return false
}

func readCache(file string) string {
	month := time.Now().Month().String()
	cacheDir := getCacheDir() + month + "/"
	file = cacheDir + file + ".json"

	if !checkFileIsExist(file) {
		return ""
	}

	bt, err := ioutil.ReadFile(file)
	err = os.Remove(file)
	if err == nil {
		return string(bt)
	}
	return ""
}

func getCacheDir() string {
	return getPWD() + "/.cache/"
}

func getPWD() string {
	path, err := os.Getwd()
	if err != nil {
		return ""
	}
	return path
}

func checkFileIsExist(filename string) bool {
	var exist = true
	if _, err := os.Stat(filename); os.IsNotExist(err) {
		exist = false
	}
	return exist
}

func (s *CaptchaService) SlideGet() (interface{}, error) {
	if s.config.Token == "" {
		return nil, errors.New("无效的 API Token")
	}

	url := fmt.Sprintf("%s/api/captcha/slide/get", s.config.ApiURL)
	var res types.BizVo
	r, err := s.client.R().
		SetHeader("AppId", s.config.AppId).
		SetHeader("Authorization", fmt.Sprintf("Bearer %s", s.config.Token)).
		SetSuccessResult(&res).Get(url)
	if err != nil || r.IsErrorState() {
		return nil, fmt.Errorf("请求 API 失败：%v", err)
	}

	if res.Code != types.Success {
		return nil, fmt.Errorf("请求 API 失败：%s", res.Message)
	}

	return res.Data, nil
}

func (s *CaptchaService) SlideCheck(data interface{}) bool {
	url := fmt.Sprintf("%s/api/captcha/slide/check", s.config.ApiURL)
	var res types.BizVo
	r, err := s.client.R().
		SetHeader("AppId", s.config.AppId).
		SetHeader("Authorization", fmt.Sprintf("Bearer %s", s.config.Token)).
		SetBodyJsonMarshal(data).
		SetSuccessResult(&res).Post(url)
	if err != nil || r.IsErrorState() {
		return false
	}

	if res.Code != types.Success {
		return false
	}

	return true
}
